package com.ly.code.web;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.po.LikeTable;
import com.ly.common.biz.BizException;
import com.ly.common.util.ZipUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.StatementCallback;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @program: ly-common
 * @description:
 * @author: 作者
 * @create: 2022-11-25 11:34
 */
@RestController
public class CodeDo {

    @Value("${ly.code.save-path}")
    private String savePath;
    @Value("${ly.code.url}")
    private String url;
    @Value("${ly.code.username}")
    private String username;
    @Value("${ly.code.password}")
    private String password;

    private String dbname;

    @Resource(name = "codeJdbcTemplate")
    JdbcTemplate jdbc;

    @PostConstruct
    public void init() {
        dbname = url.replaceAll("jdbc:mysql://[^/]*/([^\\?]+).*", "$1");
    }

    @ExceptionHandler
    public void ExHandle(Exception e, HttpServletResponse resp) throws IOException {
        resp.setContentType(MediaType.TEXT_HTML_VALUE + ";charset=utf-8");
        PrintWriter out = resp.getWriter();
        out.append("<h2 style='display:flex;justify-content:space-between;padding: 10px 20px 0px;'>");
        if (e instanceof SQLException) {
            out.append("数据库脚本异常");
        } else {
            out.append("代码生成失败");
        }
        out.append("<button onclick='history.back()' style='padding: 1px 15px;'> 返 回 </button></h2>");
        out.append("<pre style='background-color:#eee; padding:10px'>");
        e.printStackTrace(out);
        out.append("</pre>");
    }

    @PostMapping("code/generate")
    public synchronized void generate(String packagePath, @RequestParam("sql") MultipartFile sql, HttpServletResponse response) throws BizException {
        // 验证参数
        if (packagePath.matches("\\w+(\\.\\w+){2,}") == false) {
            throw new BizException("包路径格式错误, 参考 com.yc.demo");
        }
        // 删除数据库表
        delTable();
        // 删除文件
        delFile();
        // 保存sql文件
        String sqlFilepath = saveFile(sql);
        // 执行sql文件
        createTable(sqlFilepath);
        // 生成文件
        generateCode(packagePath);
        // 下载 zip
        downFile(response, sql.getOriginalFilename());
    }

    private void delFile(File... dirs) {
        File dir;
        if (dirs.length > 0) {
            dir = dirs[0];
        } else {
            dir = new File(savePath);
            if (dir.exists() == false) {
                return;
            }
        }
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                delFile(file);
            }
            System.out.printf("CodeDo.删除%s: %s\n", file.isFile() ? "文件" : "目录", file);
            file.delete();
        }
    }

    private void downFile(HttpServletResponse response, String name) throws BizException {
        System.out.println("CodeDo.打包ZIP: " + savePath + "/code");
        name += ".zip";
        try (FileOutputStream fos = new FileOutputStream(savePath + "/" + name)){
            if (response != null) {
                ZipUtils.zip(fos, savePath + "/code");
//                response.setHeader(
//                        "content-disposition",
//                        "attachment;filename=" + URLEncoder.encode(name, "UTF-8") + ".zip");
//                ZipUtils.zip(response.getOutputStream(), savePath + "/code");
                response.sendRedirect("/code/down/" + URLEncoder.encode(name, "UTF-8"));
            }
        } catch (IOException e) {
            throw new BizException("打包ZIP失败", e);
        }
    }

    void createTable(String filepath) {
        System.out.println("CodeDo.导入SQL: " + filepath);
        jdbc.execute((StatementCallback<?>) stat -> {
            try (FileReader fr = new FileReader(filepath);
                 BufferedReader br = new BufferedReader(fr);) {
                String line;
                StringBuilder sql = new StringBuilder();
                while ((line = br.readLine()) != null) {
                    if (sql.length() == 0 && !line.toLowerCase().startsWith("create ") && !line.toLowerCase().startsWith("set ")) {
                        continue;
                    }
                    sql.append(line + "\n");
                    if (line.endsWith(";")) {
                        System.out.println("========== 开始执行SQL ==========");
                        System.out.print(sql);
                        stat.execute(sql.toString());
                        System.out.println("========== 执行SQL结束 ==========\n");
                        sql.delete(0, sql.length());
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        });

    }

    private String saveFile(MultipartFile sql) throws BizException {
        File dir = new File(savePath + "/code");
        if(dir.exists() == false){
            dir.mkdirs();
        }
        File file = new File(dir, sql.getOriginalFilename());
        try {
            sql.transferTo(file);
            System.out.println("CodeDo.保存SQL: " + file);
            return file.getAbsolutePath();
        } catch (IOException e) {
            throw new BizException("SQL文件保存失败", e);
        }
    }

    private void delTable() {
        List<String> sqls = new ArrayList<>();
        String sql = "select TABLE_NAME tn, CONSTRAINT_NAME cn" +
                "  from information_schema.TABLE_CONSTRAINTS" +
                " where CONSTRAINT_TYPE='FOREIGN KEY'" +
                "   and CONSTRAINT_SCHEMA='" + dbname + "'";
        List<Map<String, Object>> maps = jdbc.queryForList(sql);
        List<String> drops = null;
        if (maps.isEmpty() == false) {
            drops = maps.stream()
                    .map(m -> "alter table " + m.get("tn") + " drop FOREIGN KEY " + m.get("cn")).collect(Collectors.toList());
            for (String dropTable : drops) {
                System.out.println("CodeDo.移除外键: " + dropTable);
            }
            sqls.addAll(drops);
            drops.clear();
        } else {
            System.out.println("CodeDo.移除外键: 无外键");
        }
        sql = "SELECT TABLE_NAME `name`\n" +
                "FROM information_schema.TABLES\n" +
                "WHERE TABLE_TYPE = 'BASE TABLE' and TABLE_SCHEMA = '" + dbname + "'";
        maps = jdbc.queryForList(sql);
        if (maps.isEmpty() == false) {
            drops = maps.stream().map(m -> "drop table " + m.get("name")).collect(Collectors.toList());
            for (String dropTable : drops) {
                System.out.println("CodeDo.移除表: " + dropTable);
            }
            sqls.addAll(drops);
        } else {
            System.out.println("CodeDo.移除表: 无表");
        }

        sql = "SELECT TABLE_NAME `name`\n" +
                "FROM information_schema.VIEWS\n" +
                "WHERE TABLE_SCHEMA = '" + dbname + "'";
        maps = jdbc.queryForList(sql);
        if (maps.isEmpty() == false) {
            drops = maps.stream().map(m -> "drop view " + m.get("name")).collect(Collectors.toList());
            for (String dropTable : drops) {
                System.out.println("CodeDo.移除表: " + dropTable);
            }
            sqls.addAll(drops);
        } else {
            System.out.println("CodeDo.移除视图: 无视图");
        }

        if (sqls.isEmpty() == false) {
            jdbc.batchUpdate(sqls.toArray(new String[sqls.size()]));
        }
    }

    private void generateCode(String packagePath) {
        int i = packagePath.lastIndexOf(".");
        String module = packagePath.substring(i + 1);
        String parent = packagePath.substring(0, i);
        String javapath = savePath + "/code/java";
        String resopath = savePath + "/code/resources/" + packagePath.replaceAll("\\.", "/") + "/mapper";
        FastAutoGenerator.create(url, username, password)
                .globalConfig(builder -> {
                    builder.author("廖老师") // 设置作者
                            .fileOverride() // 覆盖已生成文件
                            .outputDir(javapath); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent(parent) // 设置父包名
                            .moduleName(module) // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.mapperXml, resopath)); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.likeTable(new LikeTable("%"));
                })
                .execute();
    }
}
